package fischl.log;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

public final class AndroidLogs {
  private static final List<LogListener> sListeners = new ArrayList<>();
  private static final List<String> sLogs = new LinkedList<>();
  private static final int MAX_LOG_COUNT = 2000;
  private static final int MAX_BUFFER_SIZE = 2 * 1024 * 1024;
  private static final Charset utf8 = Charset.forName("UTF-8");
  private static int bufferSize = 0;

  public static void addLogListener(LogListener listener) {
    sListeners.add(listener);
  }

  public static void removeLogListener(LogListener listener) {
    sListeners.remove(listener);
  }

  public static void init() {
    LogFactory.setProvider(new AndroidLogProvider());
  }

  public static List<String> getAllLogs() {
    return new ArrayList<>(sLogs);
  }

  private static void log(String log) {
    synchronized (AndroidLogs.class) {
      if (sLogs.size() >= MAX_LOG_COUNT) {
        sLogs.remove(0);
        dispatchRemove(1);
      }
      int byteCount = stringByteCount(log);
      int size = bufferSize + byteCount;
      Iterator<String> i = sLogs.iterator();
      int remove = 0;
      while(size > MAX_BUFFER_SIZE && i.hasNext()) {
        int count = stringByteCount(i.next());
        size -= count;
        remove++;
        i.remove();
      }
      if (remove > 0) {
        dispatchRemove(remove);
      }
      bufferSize = size;
      sLogs.add(log);
    }
    for (LogListener listener : sListeners) {
      listener.onNewLog(log);
    }
  }

  public static int getBufferSize() {
    return bufferSize;
  }

  private static void dispatchRemove(int count) {
    for (LogListener listener : sListeners) {
      listener.onRemoveLog(count);
    }
  }

  private static int stringByteCount(String text) {
    return text.getBytes(utf8).length;
  }

  static class AndroidLog implements Log {
    private final String tag;
    private final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss", Locale.CHINA);

    public AndroidLog(String tag) {
      this.tag = tag;
    }

    @Override
    public void i(String message) {
      logLine("I", message);
    }

    @Override
    public void w(String message) {
      logLine("W", message);
    }

    @Override
    public void e(String message) {
      logLine("E", message);
    }

    @Override
    public void e(Throwable t) {
      e("", t);
    }

    @Override
    public void e(String message, Throwable t) {
      logLine("E", message);
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      try {
        PrintStream p = new PrintStream(out, true, "UTF-8");
        t.printStackTrace(p);
        p.flush();
        log(out.toString("UTF-8"));
      } catch (Exception e) {
        //ignored
      }
    }

    private void logLine(String level, String message) {
      String threadName = Thread.currentThread().getName();
      if(threadName.length() > 8) {
        threadName = threadName.substring(0, 6) + "..";
      } else {
        char[] blankArr = new char[8 - threadName.length()];
        Arrays.fill(blankArr, ' ');
        threadName = threadName + new String(blankArr);
      }

      String logTime = format.format(new Date());
      String tag = this.tag;
      StringBuilder output = new StringBuilder(level).append("[").append(logTime).append("|").append(threadName).append("]").append(tag).append(": ").append(message);
      log(output.toString());
    }

    @Override
    public void i(String message, Object... args) {
      logLine("I", replaceArgs(message, args));
    }


    @Override
    public void w(String message, Object... args) {
      logLine("W", replaceArgs(message, args));
    }


    @Override
    public void e(String message, Object... args) {
      logLine("E", replaceArgs(message, args));
    }

    static String replaceArgs(String message, Object... args) {
      if (args == null) return message;
      String[] argsStrings = new String[args.length];
      for (int i = 0; i < args.length; i++) {
        argsStrings[i] = String.valueOf(args[i]);
      }
      message = message.replaceAll("\\{\\}", "%s");
      return String.format(message, (Object[]) argsStrings);
    }
  }

  static class AndroidLogProvider implements Log.Provider {
    @Override
    public Log getLog(String name) {
      return new AndroidLog(name);
    }
  }

  public interface LogListener {
    void onNewLog(String line);

    void onRemoveLog(int count);
  }
}
